Aprenda a implementar uma estratégia robusta de tratamento de erros no React usando Árvores de Error Boundaries para degradação graciosa e melhor experiência do usuário.
Árvore de Error Boundaries no React: Tratamento de Erros Hierárquico para Aplicações Robustas
A arquitetura baseada em componentes do React promove a reutilização e a manutenibilidade, mas também introduz o potencial de propagação de erros que podem perturbar toda a aplicação. Erros não tratados podem levar a uma experiência chocante para os usuários, exibindo mensagens enigmáticas ou até mesmo travando a aplicação. Os Error Boundaries fornecem um mecanismo para capturar erros de JavaScript em qualquer lugar na sua árvore de componentes filhos, registrar esses erros e exibir uma UI de fallback em vez da árvore de componentes que falhou. Uma Árvore de Error Boundaries bem projetada permite isolar falhas e fornecer uma melhor experiência ao usuário, degradando graciosamente seções específicas da sua aplicação sem afetar as outras.
Entendendo os Error Boundaries do React
Introduzidos no React 16, os Error Boundaries são componentes React que capturam erros de JavaScript em qualquer lugar na sua árvore de componentes filhos, registram esses erros e exibem uma UI de fallback em vez da árvore de componentes que falhou. Os Error Boundaries capturam erros durante a renderização, em métodos de ciclo de vida e nos construtores de toda a árvore abaixo deles. Crucialmente, eles *não* capturam erros para:
- Manipuladores de eventos (saiba mais abaixo)
- Código assíncrono (ex: callbacks de
setTimeoutourequestAnimationFrame) - Renderização no lado do servidor (Server-Side Rendering)
- Erros lançados no próprio error boundary (em vez de em seus filhos)
Um componente de classe se torna um Error Boundary se ele definir um (ou ambos) destes métodos de ciclo de vida:
static getDerivedStateFromError(): Este método é invocado após um erro ter sido lançado por um componente descendente. Ele recebe o erro que foi lançado como argumento e deve retornar um valor para atualizar o estado.componentDidCatch(): Este método é invocado após um erro ter sido lançado por um componente descendente. Ele recebe dois argumentos:error: O erro que foi lançado.info: Um objeto contendo informações sobre qual componente lançou o erro.
Um Exemplo Simples de Error Boundary
Aqui está um componente Error Boundary básico:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Atualiza o estado para que a próxima renderização mostre a UI de fallback.
return { hasError: true };
}
componentDidCatch(error, info) {
// Você também pode registrar o erro em um serviço de relatórios de erros
console.error("Caught an error: ", error, info.componentStack);
//logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Você pode renderizar qualquer UI de fallback personalizada
return <h1>Algo deu errado.</h1>;
}
return this.props.children;
}
}
Uso:
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
O Poder da Árvore de Error Boundaries
Embora um único Error Boundary possa proteger toda a sua aplicação, uma abordagem mais sofisticada envolve a criação de uma *Árvore* de Error Boundaries. Isso significa posicionar estrategicamente múltiplos Error Boundaries em diferentes níveis da hierarquia de seus componentes. Isso permite que você:
- Isole Falhas: Uma falha em uma parte da aplicação não necessariamente derrubará toda a UI. Apenas a porção envolvida pelo Error Boundary específico exibirá a UI de fallback.
- Forneça Fallbacks Específicos ao Contexto: Diferentes partes da sua aplicação podem exigir UIs de fallback diferentes. Por exemplo, um componente de imagem que falha pode exibir uma imagem de placeholder, enquanto um componente de busca de dados que falha pode exibir um botão "Tentar Novamente".
- Melhore a Experiência do Usuário: Ao posicionar cuidadosamente os Error Boundaries, você pode garantir que sua aplicação se degrade graciosamente, minimizando a interrupção para o usuário.
Construindo uma Árvore de Error Boundaries: Um Exemplo Prático
Vamos considerar uma aplicação web que exibe um perfil de usuário. O perfil consiste em várias seções:
- Informações do Usuário (nome, localização, biografia)
- Foto de Perfil
- Feed de Atividades Recentes
- Lista de Seguidores
Podemos envolver cada uma dessas seções com seu próprio Error Boundary.
// ErrorBoundary.js (O componente ErrorBoundary genérico de cima)
import ErrorBoundary from './ErrorBoundary';
function UserProfile() {
return (
<div>
<ErrorBoundary>
<UserInfo />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<img src="/placeholder.png" alt="Espaço reservado"/>}>
<ProfilePicture />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<p>Falha ao carregar a atividade. Por favor, tente novamente mais tarde.</p>}>
<ActivityFeed />
</ErrorBoundary>
<ErrorBoundary fallbackUI={<p>Não foi possível carregar os seguidores.</p>}>
<FollowersList />
</ErrorBoundary>
</div>
);
}
Neste exemplo, se o componente ProfilePicture falhar ao carregar (ex: devido a um URL de imagem quebrado), apenas a área da foto de perfil exibirá a UI de fallback (a imagem de placeholder). O resto do perfil permanecerá funcional. Da mesma forma, uma falha no componente ActivityFeed afetará apenas essa seção, exibindo uma mensagem "Por favor, tente novamente mais tarde".
Note o uso da prop fallbackUI em alguns dos componentes ErrorBoundary. Isso nos permite personalizar a UI de fallback para cada seção, proporcionando uma experiência mais consciente do contexto e amigável ao usuário.
Técnicas Avançadas de Error Boundary
1. Personalizando a UI de Fallback
A UI de fallback padrão (ex: uma simples mensagem "Algo deu errado") pode não ser suficiente para todos os cenários. Você pode personalizar a UI de fallback para fornecer mensagens mais informativas, oferecer ações alternativas ou até mesmo tentar se recuperar do erro.
Como mostrado no exemplo anterior, você pode usar props para passar uma UI de fallback personalizada para o componente ErrorBoundary:
<ErrorBoundary fallbackUI={<CustomFallbackComponent />}>
<MyComponent />
</ErrorBoundary>
O CustomFallbackComponent pode exibir uma mensagem de erro mais específica, sugerir etapas de solução de problemas ou oferecer um botão "Tentar Novamente".
2. Registrando Erros em Serviços Externos
Embora os Error Boundaries evitem que a aplicação trave, é crucial registrar os erros para que você possa identificar e corrigir os problemas subjacentes. O método componentDidCatch é o local ideal para registrar erros em serviços externos de rastreamento de erros como Sentry, Bugsnag ou Rollbar.
class ErrorBoundary extends React.Component {
// ...
componentDidCatch(error, info) {
// Registra o erro em um serviço de relatórios de erros
logErrorToMyService(error, info.componentStack);
}
// ...
}
Certifique-se de configurar seu serviço de rastreamento de erros para lidar com erros de JavaScript e fornecer informações detalhadas sobre o erro, incluindo o stack trace do componente.
Exemplo usando Sentry:
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
integrations: [new BrowserTracing()],
// Defina tracesSampleRate como 1.0 para capturar 100%
// das transações para monitoramento de desempenho.
// Recomendamos ajustar este valor em produção
tracesSampleRate: 1.0,
});
class ErrorBoundary extends React.Component {
// ...
componentDidCatch(error, info) {
Sentry.captureException(error, { extra: info });
}
// ...
}
3. Error Boundaries e Manipuladores de Eventos
Como mencionado anteriormente, os Error Boundaries *não* capturam erros dentro de manipuladores de eventos. Isso ocorre porque os manipuladores de eventos são executados de forma assíncrona, fora do ciclo de vida de renderização do React. Para tratar erros em manipuladores de eventos, você precisa usar um bloco try...catch.
function MyComponent() {
const handleClick = () => {
try {
// Código que pode lançar um erro
throw new Error("Something went wrong in the event handler!");
} catch (error) {
console.error("Error in event handler:", error);
// Exibe uma mensagem de erro para o usuário
alert("Ocorreu um erro. Por favor, tente novamente.");
}
};
return <button onClick={handleClick}>Clique Aqui</button>;
}
4. Error Boundaries e Operações Assíncronas
Da mesma forma, os Error Boundaries não capturam erros em operações assíncronas como setTimeout, setInterval ou Promises. Você precisa usar blocos try...catch dentro dessas operações assíncronas para tratar os erros.
Exemplo com Promises:
function MyComponent() {
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Processa os dados
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
// Exibe uma mensagem de erro para o usuário
alert("Falha ao buscar dados. Por favor, verifique sua conexão.");
}
};
fetchData();
}, []);
return <div>Carregando dados...</div>;
}
5. Tentando Novamente Operações com Falha
Em alguns casos, pode ser possível tentar novamente uma operação que falhou automaticamente. Por exemplo, se uma requisição de rede falhar devido a um problema temporário de conectividade, você poderia implementar um mecanismo de nova tentativa com backoff exponencial.
Você pode implementar um mecanismo de nova tentativa dentro da UI de fallback ou dentro do componente que sofreu o erro. Considere usar bibliotecas como axios-retry ou implementar sua própria lógica de nova tentativa usando setTimeout.
Exemplo (nova tentativa básica):
function RetryComponent({ onRetry }) {
return <button onClick={onRetry}>Tentar Novamente</button>;
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
console.error("Caught an error: ", error, info.componentStack);
}
handleRetry = () => {
this.setState({ hasError: false, error: null }, () => {
// Força a re-renderização do componente atualizando o estado
this.forceUpdate();
});
};
render() {
if (this.state.hasError) {
return (
<div>
<h1>Algo deu errado.</h1>
<p>{this.state.error?.message}</p>
<RetryComponent onRetry={this.handleRetry} />
</div>
);
}
return this.props.children;
}
}
Melhores Práticas para Usar Error Boundaries
- Envolva Rotas Inteiras: Para rotas de nível superior, considere envolver toda a rota com um Error Boundary para capturar quaisquer erros inesperados que possam ocorrer. Isso fornece uma rede de segurança e impede que toda a aplicação trave.
- Envolva Seções Críticas: Identifique as seções mais críticas da sua aplicação (ex: o processo de checkout em um site de e-commerce) e envolva-as com Error Boundaries para garantir que sejam resilientes a erros.
- Não Use Error Boundaries em Excesso: Evite envolver cada componente com um Error Boundary. Isso pode adicionar uma sobrecarga desnecessária e tornar seu código mais difícil de ler. Concentre-se em envolver componentes que são propensos a falhas ou que são críticos para a experiência do usuário.
- Forneça UIs de Fallback Informativas: A UI de fallback deve fornecer informações claras e úteis ao usuário sobre o que deu errado e o que eles podem fazer para resolver o problema. Evite exibir mensagens de erro genéricas que não fornecem nenhum contexto.
- Registre Erros Completamente: Certifique-se de registrar todos os erros capturados pelos Error Boundaries em um serviço externo de rastreamento de erros. Isso ajudará você a identificar e corrigir problemas subjacentes rapidamente.
- Teste Seus Error Boundaries: Escreva testes de unidade e testes de integração para garantir que seus Error Boundaries estejam funcionando corretamente e que estejam capturando os erros esperados. Simule condições de erro e verifique se a UI de fallback é exibida corretamente.
- Considere o Tratamento de Erros Global: Embora os Error Boundaries sejam ótimos para tratar erros dentro dos componentes React, você também deve considerar a implementação de um tratamento de erros global para capturar erros que ocorrem fora da árvore do React (ex: rejeições de promise não tratadas).
Considerações Globais e Sensibilidade Cultural
Ao projetar Árvores de Error Boundaries para um público global, é essencial considerar a sensibilidade cultural e a localização:
- Localização: Garanta que suas UIs de fallback sejam devidamente localizadas para diferentes idiomas e regiões. Use uma biblioteca de localização como
i18nextoureact-intlpara traduzir mensagens de erro e outros textos. - Contexto Cultural: Esteja ciente das diferenças culturais ao projetar suas UIs de fallback. Evite usar imagens ou símbolos que possam ser ofensivos ou inadequados em certas culturas. Por exemplo, um gesto de mão que é considerado positivo em uma cultura pode ser ofensivo em outra.
- Fusos Horários: Se suas mensagens de erro incluírem timestamps ou outras informações relacionadas ao tempo, certifique-se de exibi-las no fuso horário local do usuário.
- Moedas: Se suas mensagens de erro envolverem valores monetários, exiba-os na moeda local do usuário.
- Acessibilidade: Garanta que suas UIs de fallback sejam acessíveis a usuários com deficiência. Use atributos ARIA apropriados e siga as diretrizes de acessibilidade para tornar sua aplicação utilizável por todos.
- Opt-In para Relatórios de Erros: Seja transparente sobre o relatório de erros. Forneça aos usuários a opção de optar por participar (opt-in) ou não (opt-out) do envio de relatórios de erros para seus servidores. Garanta a conformidade com regulamentos de privacidade como GDPR e CCPA.
Exemplo (Localização usando `i18next`):
// i18n.js (configuração do i18next)
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en/translation.json';
import fr from './locales/fr/translation.json';
i18n
.use(initReactI18next) // passa o i18n para o react-i18next
.init({
resources: {
en: { translation: en },
fr: { translation: fr },
},
lng: 'en', // idioma padrão
fallbackLng: 'en',
interpolation: {
escapeValue: false, // o react já protege contra xss
},
});
export default i18n;
// ErrorBoundary.js
import { useTranslation } from 'react-i18next';
function ErrorBoundary(props) {
const { t } = useTranslation();
// ...
render() {
if (this.state.hasError) {
return <h1>{t('error.somethingWentWrong')}</h1>;
}
return this.props.children;
}
}
Conclusão
As Árvores de Error Boundaries do React são uma ferramenta poderosa para construir aplicações robustas e resilientes. Ao posicionar estrategicamente os Error Boundaries em diferentes níveis da hierarquia de seus componentes, você pode isolar falhas, fornecer fallbacks específicos ao contexto e melhorar a experiência geral do usuário. Lembre-se de tratar erros em manipuladores de eventos e operações assíncronas usando blocos try...catch. Seguindo as melhores práticas e considerando fatores globais e culturais, você pode criar aplicações que são tanto confiáveis quanto amigáveis para um público diversificado.
Ao implementar uma Árvore de Error Boundaries bem projetada e prestando atenção aos detalhes, você pode melhorar significativamente a confiabilidade e a experiência do usuário de suas aplicações React, independentemente de onde seus usuários estejam localizados.